#!/usr/bin/env python
# coding: utf8
import socket
import threading
import time
import sys 
import MySQLdb
import errno
import os
import time
import random
import sys
import chardet

from stat import *
from config import *
from logger import *

try:
    import pwd
    import grp
except ImportError:
    pwd = grp = None

userConnList = {}

MAX_BUFFER = 10 * 1024 * 1024 

class ConnectionHandler(threading.Thread):
    
    def __init__(self, fd, clientIP):
        

        threading.Thread.__init__(self)
        self.clientIP = clientIP
        self.fd = fd
        self.running = True

        self.connLock = threading.Lock()

        self.setDaemon(True)
        self.aliveTime = time.time()

        self.optionUTF8 = False
        self.identified = False 
        self.optionPasv = True
        self.username = ''
        self.uid = ''
        self.gid = ''
        self.homeDir = ''
        self.groups = []

        self.perms = ''
        self.operms = {}
        self.nowdirperm = ''
        self.downloadLimit = GlobalDownloadLimit
        self.maxConn = 1
        
        self.currDir = ''
        self.currType = 'i'   #Binary mode default
        self.filePos = 0
        self.filePath = ''

        self.optionPasv = False

        self.renamedir = ''

        self.usedQuote = 0
        self.totalQuote = 0

        self.clientEncode = 'utf-8'  #Default encode is utf-8

    def run(self):
        
        global userConnList
       
        self.sendMsg(220, motd)
        
        line = ''
        while self.running:
    
            data = self.fd.recv(4096)
            if len(data) == 0:
                break

            line += data
            if line[-2:] != '\r\n':
                continue

            line = line[:-2]
            space = line.find(' ')
            try:
                if space == -1:
                    self.process(line, '')
                else:
                    self.process(line[:space], line[space + 1:])
                line = ''
            except Exception, err:
                logger.debug('Server Error: %s' % err)
                self.running = False
        
        self.running = False
        self.fd.close()
        self.fd = 0
        logger.info('===>' + 'User %s Disconncted' % self.username)
        self.connLock.acquire()
        try:
            userConnList[self.username] -= 1
        except KeyError:
            pass
        self.connLock.release()

    def process(self, cmd, arg):
        global userConnList
        global remainingQuote
        cmd = cmd.upper()

        if cmd != 'USER': 
            logger.debug('===>' + 'User %s Command:    ' % self.username + cmd + ' ' + self.serverlize(arg))

        #FTP Command Processing

        if cmd == 'USER':
            if arg == 'anonymous':
                self.sendMsg(500, 'Server not allow anonymous user.')
                logger.info('===>' + 'Anonymous login prohibited.')
                self.running = False
                return

            if arg != 'attach':
                if arg[:4] == 'gbk_':
                    self.clientEncode = 'gbk' 
                    arg = 'qsh_' + arg[4:]
                elif arg[:4] == 'qsh_':
                    arg = 'qsh_' + arg[4:]
                else:
                    arg = 'qsh_' + arg
            self.username = self.serverlize(arg)
            logger.info('===>' + 'User %s Try to Log In. IP:%s Client Encoding:%s' % (self.username, self.clientIP, self.clientEncode))
            state = self.userAuthenticator(self.username)
            if state == True:
                if not self.username in userConnList:
                    userConnList[self.username] = 0
                else:
                    if userConnList[self.username] >= self.maxConn:
                        logger.info('===>' + 'User %s Exceeded Connections Limit' % self.username)
                        self.sendMsg(500, 'Too many connections for one user')
                        self.connLock.acquire()
                        userConnList[self.username] += 1
                        self.connLock.release()
                        return
                self.sendMsg(331, 'Password for user ' + self.localize(self.username))
                self.connLock.acquire()
                userConnList[self.username] += 1
                self.connLock.release()
            else:
                logger.info('===>' + 'User %s Not Exists' % self.localize(self.username))
                self.sendMsg(530, 'No Such User!')
                self.running = False
                return

        elif cmd == 'PASS':
            self.passwd = arg
            state = self.passwdAuthenticator(self.username ,self.passwd)
            if state == True:
                self.sendMsg(230, 'User %s Login successful' % self.localize(self.username))
                logger.info('===>' + 'User %s Logged In' % self.username)
            else:
                self.sendMsg(530, 'Wrong Password!')
                logger.info('===>' + 'Password "%s" Not Correct for %s' % (self.passwd, self.username))
                self.running = False
                return

        elif cmd == 'BYE' or cmd == 'QUIT':
            self.sendMsg(221, 'Bye!')
            logger.info('===>' + 'User %s Log Off' % self.username)
            self.running = False 
            return

        elif cmd == 'ABOR':
            self.sendMsg(426, 'Connection closed. Transfer aborted.')
            self.sendMsg(227, 'ABOR command successful.')
            self.running = False
            if self.dataFd != 0:
                self.dataFd.close()
                self.dataFd = 0
            logger.info('===>' + 'User %s Above Connection' % self.username)
            return

        elif cmd == 'SYST':
            self.sendMsg(215, 'UNIX Type: L8')

        elif cmd == 'FEAT':
            features = ['EPRT', 'EPSV', 'MDTM', 'MLSD', 'REST STREAM', 'SIZE', 'TVFS', 'MLST type*;perm*;sizez*;modify*;unique*;unix.mode;unix.uid;unix.gid;']
            features.sort()
            self.fd.send('211-Features supported:\r\n')
            self.fd.send(''.join([ ' %s\r\n' %x for x in features]))
            self.sendMsg(211, 'End FEAT.')

        elif cmd == 'OPTS':
            try:
                if arg.count(' ') > 1:
                    raise ValueError('Invalid number of arguments')
                if ' ' in arg:
                    cmd, arg = arg.split(' ')
                    if ';' not in arg:
                        raise ValueError('Invalid argument')
                else:
                    cmd, arg = arg, ''

                if cmd.upper() != 'MLST':
                    raise ValueError('Unsupported command "%s"' % cmd)

            except ValueError, err:
                self.sendMsg(501, err)

            else:
                self.sendMsg(200, arg)

        elif cmd == 'PWD' or cmd == 'XPWD':
            if self.currDir == '':
                self.currDir = '/'
            
            self.sendMsg(257, '"%s" is the current directory' % self.localize(self.currDir))

        elif cmd == 'TYPE':
            type = arg.upper().replace(' ', '')

            if type in ('A', 'L7'):
                self.sendMsg(200, 'Type set to: ASCII.')
                self.currType = 'a'
            elif type in ('I', 'L8'):
                self.sendMsg(200, 'Type set to: Binary.')
                self.currType = 'i'
            else:
                self.sendMsg(504, 'Unsupported type "%s".' % arg)

        elif cmd == 'EPSV' or cmd == 'PASV':
            try:
                self.optionPasv = True
                self.dataFd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                randomPort = random.randint(PassiveRangePort[0], PassiveRangePort[1])
                self.dataFd.bind((bindIP, randomPort))
                self.dataFd.listen(1)
                ip, port = self.dataFd.getsockname()
                if cmd == 'EPSV':
                    self.sendMsg(229, 'Entering Extended Passive Mode (|||' + str(port) + '|')
                elif cmd == 'PASV':
                    ipnum = socket.inet_aton(ip)
                    self.sendMsg(227, 'Entering Passive Mode (%s,%u,%u).' % (",".join(ip.split(".")), (port>>8&0xff), (port&0xff)))
            except:
                self.sendMsg(500, 'Failed to enter passive mode.')

        elif cmd == 'PORT':
            self.optionPasv = False
            try:
                self.dataFd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                data = arg.split(',')
                self.dataIP = '.'.join(data[:4])
                self.dataPort = int(data[4]) * 256 + int(data[5])

            except:
                self.sendMsg(500, 'Cannot enter active data mode.')
            self.sendMsg(200, 'Active data connection established.')

        elif cmd == 'MLSD':                   
            try:
                nowDir = self.homeDir + self.currDir
                listing = os.listdir(nowDir)
                tye = size = perm = modify = unix_mode = unix_uid = unix_gid = name = ''

                if not self.establish():
                    return
                self.sendMsg(150, 'File status okay. About to open data connection.')
                msg = ''
                if self.username != 'attach':
                    for name in listing:
                        (parent, fullname) = self.formatPath(name)
                        if os.path.isdir(fullname):
                            tye = 'dir' 
                            perm = self.getDirPerm(fullname)
                        else:
                            tye = 'file' 
                            perm = self.getFilePerm(parent)
                        size = str(os.path.getsize(fullname))
                        mtime = os.stat(fullname).st_mtime
                        tmp = time.localtime(mtime)
                        modify = '%.4d%.2d%.2d%.2d%.2d%.2d' % (tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5])
                        unix_mode = str(oct(os.stat(fullname)[ST_MODE])[-4:])
                        unix_uid = str(os.stat(fullname).st_uid)
                        unix_gid = str(os.stat(fullname).st_gid)
                        
                        if tye == 'file':
                            msg += 'type=' + tye + ';size=' + size + ';perm=' + perm + ';modify=' + modify + ';unix.mode=' + unix_mode + ';unix_uid=' + unix_uid + ';unix_gid=' + unix_gid + '; ' + name + '\r\n'
                        elif 'l' in perm:
                            msg += 'type=' + tye + ';size=' + size + ';perm=' + perm + ';modify=' + modify + ';unix.mode=' + unix_mode + ';unix_uid=' + unix_uid + ';unix_gid=' + unix_gid + '; ' + name + '\r\n'
            
                self.dataFd.send(self.localize(msg))
                self.dataFd.close()
                self.dataFd = 0

                self.sendMsg(226, 'Transfer complete.')

            except OSError, err:
                self.sendMsg(500, err)

        elif cmd == 'LIST' or cmd == 'NLST':
            if not self.establish():
                return 
            self.sendMsg(125, 'File status okay. About to open data connection.')
            msg = ''
            if self.username != 'attach':
                nowDir = self.homeDir + self.currDir
                os.system('ls -l ' + nowDir + ' > /tmp/dirlist')
                listfd = open('/tmp/dirlist', 'rb')
                filelist = listfd.readlines()
                l = []
                for every in filelist[1:]:
                    l = every.split()
                    msg += '%s%4u %s %8s %10u %s %s\r\n' % (l[0], int(l[1]), l[2], l[3], int(l[4]), l[5] + ' ' + l[6] + ' ' + l[7], l[8])
            
            self.dataFd.send(self.localize(msg))
            self.dataFd.close()
            self.dataFd = 0
            
            time.sleep(0.1)
            self.sendMsg(226, 'Transfer complete.')

        elif cmd == 'MDTM':
            self.serverlize(arg)
            (parent, fullname) = self.formatPath(arg)
            if os.path.exists(fullname):
                self.sendMsg(213, time.strftime("%Y%m%d%I%M%S", time.localtime(os.path.getmtime(fullname))))
            else:
                self.sendMsg(550, 'MDTM Failed.')

        elif cmd == 'CWD' or cmd == 'CDUP':
            if cmd != 'CDUP':
                arg = self.serverlize(arg)
            if '..' in arg or cmd == 'CDUP':
                if self.currDir == '/':
                    newpath = '/'
                else:
                    newpath = '/'.join(self.currDir.split('/')[0:-1])
            elif arg[0] == '/':
                newpath = arg
            elif self.currDir == '/':
                newpath = self.currDir + arg
            elif self.currDir != '/':
                newpath = self.currDir + '/' + arg
            if self.canEnter(newpath) or self.username == 'attach':
                self.currDir = newpath

                if self.currDir == '':
                    self.currDir = '/'

                self.sendMsg(250, '"%s" is the current directory.' % self.localize(self.currDir))
            else:
                self.sendMsg(550, 'Cannot enter directory "%s". No enough privileges.' % self.localize(newpath))
                return

        elif cmd == 'RETR':
            arg = self.serverlize(arg)
            (parent, filePath) = self.formatPath(arg)
            logger.info('===>' + 'User %s Try to Download File %s' % (self.username, filePath.split('/')[-1]))
            if not os.path.isfile(filePath):
                self.sendMsg(550, 'File not found')
                self.running = False
                return
            if not self.canRead(parent):
                self.sendMsg(550, "Can't RETR. No enough privileges.")
                logger.info('===>' + 'Permission Denied for %s' % self.username)
                self.running = False
                return
            if not self.establish():
                return
            self.sendMsg(150, 'File status okay. About to open data connection.')
            logger.info('===>' + 'User %s Started Download File %s' % (self.username, filePath.split('/')[-1]))

            self.fileFd = open(filePath, 'rb')
            self.fileFd.seek(self.filePos)
            (self.remainingQuote, self.usedQuote, self.totalQuote) = self.getRemainQuote()
            self.updateFileCount(filePath.split('/')[-1])
            self.filePath = filePath

            downloader = fileDownloader(self)
            downloader.start()

        elif cmd == 'REST':
            self.filePos = int(arg)
            self.sendMsg(350, 'Restarting at position %d' % self.filePos)

        elif cmd == 'SIZE':
            (path, fullname) = self.formatPath(arg)

            if os.path.exists(fullname):
                self.sendMsg(231, os.path.getsize(fullname))
            else:
                self.sendMsg(231, 0)

        elif cmd == 'STOR' or cmd == 'APPE' or cmd == 'STOU':
            arg = self.serverlize(arg)
            (path, fullname) = self.formatPath(arg)
            self.fullname = fullname
            logger.info('===>' + 'User %s Try to Upload File %s' % (self.username, fullname))
            
            if not self.canWrite(path) and self.username != 'attach':
                self.sendMsg(550, "Can't write, no enough privileges.")
                logger.info('===>' + 'Permission Denied for %s' % self.username)
                self.running = False
                return 
            if os.path.exists(fullname):
                if self.username == 'attach':
                    self.sendMsg(550, "File Already Exists.")
                    logger.info('===>' + 'Attach file already exists for user attch')
                    self.running = False
                    return 

                perm = self.getFilePerm(path)
                if cmd == 'STOU':
                    self.sendMsg(550, 'Filename must be unique.')
                    logger.info('===>' + 'Permission Denied for %s' % self.username)
                    return
                if cmd == 'STOR' and self.username != 'attach':
                    if 'w' not in perm:
                        self.sendMsg(550, "Can't resume %s, no enough privileges." % arg)
                        logger.info('===>' + 'Permission Denied for %s' % self.username)
                        self.running = False
                        return 
                if cmd == 'APPE' and self.username != 'attach':
                    if 'a' not in perm:
                        self.sendMsg(550, "Can't resume %s, no enough privileges." % arg)
                        logger.info('===>' + 'Permission Denied for %s' % self.username)
                        self.running = False
                        return 
           
            if self.username == 'attach':
                filetype = fullname.split('.')[-1]
                if filetype not in ATTACH_FILETYPE:
                    self.sendMsg(550, "Forbidden File type")
                    logger.info('===>' + 'Forbidden File Type: %s for attch' % filetype)
                    self.running = False
                    return 
            if not self.establish():
                return

            self.sendMsg(150, 'File status okay. About to open data connection.')
            logger.info('===>' + 'User %s Started Upload File %s.' % (self.username, fullname))
            if cmd == 'APPE' or self.filePos != 0:
                self.recvFile = open(fullname, 'ab')
            else:
                self.recvFile = open(fullname, 'wb')

            os.chmod(fullname, 0644)

            self.filepath = self.currDir + '/' + arg
            uploader = fileUploader(self)
            uploader.start()

        elif cmd == 'RNFR':
            (path, fullname) = self.formatPath(arg)
            logger.info('===>' + 'User %s Try to Rename File %s' % (self.username, fullname))
            
            if os.path.isdir(fullname):
                if not self.canRename(path):
                    self.sendMsg(550, "Can't RNFR, no enough privileges.")
                    logger.info('===>' + 'Permission Denied for %s' % self.username)
                    return 
            else:
                perm = self.getFilePerm(fullname)
                if 'f' not in perm:
                    self.sendMsg(550, "Can't RNFR, no enough privileges.")
                    logger.info('===>' + 'Permission Denied for %s' % self.username)
                    return 

            self.renamedir = fullname
            self.sendMsg(350, 'Ready for destination name.')

        elif cmd == 'RNTO':
            if arg[0] != '/':
                (path, fullname) = self.formatPath(arg)
            else:
                fullname = self.homeDir + arg
                path = self.homeDir

            if os.path.isdir(fullname):
                if not self.canRename(path):
                    self.sendMsg(550, "Can't RNTO, no enough privileges.")
                    logger.info('===>' + 'Permission Denied for %s' % self.username)
                    return 
            else:
                perm = self.getFilePerm(fullname)
                if 'f' not in perm:
                    self.sendMsg(550, "Can't RNTO, no enough privileges.")
                    logger.info('===>' + 'Permission Denied for %s' % self.username)
                    return 
            
            os.rename(self.renamedir, fullname)

            self.sendMsg(250, 'Renaming OK.')
            logger.info('===>' + 'User %s Renamed File %s to %s' % (self.username, self.renamedir, fullname))

        elif cmd == 'DELE':
            arg = self.serverlize(arg)
            (path, fullname) = self.formatPath(arg)
            logger.info('===>' + 'User %s Try to Delete File %s' % (self.username, fullname))

            perm = self.getFilePerm(fullname)
            if 'd' not in perm:
                self.sendMsg(550, "Can't DELE. No enough privileges")
                logger.info('===>' + 'Permission Denied for %s' % self.username)
                return
            else:
                os.remove(fullname)
                self.sendMsg(250, 'File removed.')
                logger.info('===>' + 'User %s Deleted File %s' % (self.username, fullname))
        elif cmd == 'RMD':
            (path, fullname) = self.formatPath(arg)
            logger.info('===>' + 'User %s Try to Delete Dir %s' % (self.username, fullname))

            if self.canRemove(path):
                os.rmdir(fullname)
                self.sendMsg(250, 'Directory removed.')
                logger.info('===>' + 'User %s Deleted Dir %s' % (self.username, fullname))
            else:
                self.sendMsg(550, "Can't RMD. No enough privileges")
                logger.info('===>' + 'Permission Denied for %s' % self.username)
                return

        elif cmd == 'MKD':
            (path, fullname) = self.formatPath(arg)
            logger.info('===>' + 'User %s Try to Make Dir %s' % (self.username, fullname))

            if self.canMkDir(path):
                os.mkdir(fullname)
                self.sendMsg(256, '"%s" directory created' % arg)
                logger.info('===>' + 'User %s Made Dir %s' % (self.username, fullname))
            else:
                self.sendMsg(550, "Can't MKD. No enough Privileges")
                logger.info('===>' + 'Permission Denied for %s' % self.username)
                return

        else:
            self.sendMsg(500, '"%s" is not supported by this server.' % cmd)
            logger.info('===>' + 'User %s Sent Unknown Command %s' % (self.username, cmd))

        self.aliveTime = time.time()

    def userAuthenticator(self, username):

        def authEnd(state):
            cursor.close()
            dbConn.close()
            return state

        dbConn = MySQLdb.connect( host = dbhost, user = dbuser, passwd = dbpasswd, db = dbname)
        cursor = dbConn.cursor()

        cmd = 'SELECT * FROM ftpuser WHERE userid="%s";' % MySQLdb.escape_string(username)
        cursor.execute(cmd)

        rowUser = cursor.fetchone()
        
        cmd = 'SELECT * FROM ftpgroup WHERE members="%s";' % MySQLdb.escape_string(username)
        cursor.execute(cmd)
        groups = cursor.fetchall()
        for everyone in groups:
            self.groups.append(everyone[1])

        self.permProducer()

        if rowUser == None:
            return authEnd(False)

        else:
            return authEnd(True)

    def passwdAuthenticator(self, username, passwd):

        def authEnd(state):
            cursor.close()
            dbConn.close()
            return state

        dbConn = MySQLdb.connect( host = dbhost, user = dbuser, passwd = dbpasswd, db = dbname)
        cursor = dbConn.cursor()

        cmd = 'SELECT * FROM ftpuser WHERE userid="%s";' % MySQLdb.escape_string(username)
        cursor.execute(cmd)

        rowUser = cursor.fetchone()
        self.uid = rowUser[3]
        self.gid = rowUser[4]
        self.homeDir = rowUser[5]
        if self.homeDir[-1] == '/':
            self.homeDir = self.homeDir[:-1]

        if rowUser[2] == passwd:
            return authEnd(True)

        else:
            return authEnd(False)

    def establish(self):
        if self.dataFd == 0:
            self.sendMsg(500, 'No data connection.')
            return False
        
        if self.optionPasv:
            fd = self.dataFd.accept()[0]
            self.dataFd.close()
            self.dataFd = fd
        else:
            try:
                self.dataFd.connect((self.dataIP, self.dataPort))
            except:
                self.sendMsg(500, 'Failed to establish data connection')
                return False
        
        return True
    
    def sendMsg(self, code, s):

        txt = str(s).replace('\r', '')
        txt = txt.split('\n')

        if len(txt) > 1:
            msg = (str(code) + '-') + ('\r\n' + str(code) + '-').join(txt[:-1])
            msg += '\r\n' + str(code) + ' ' + txt[-1] + '\r\n'
        else:
            msg = str(code) + ' ' + txt[0] + '\r\n'
        
        if self.optionUTF8:
            msg = unicode(msg, sys.getfilesystemencoding()).encode("utf8")
        
        self.fd.send(msg)

    def permProducer(self):
        for every in self.groups:
            if self.downloadLimit <permission[every]['download_limit']:
                self.downloadLimit = permission[every]['download_limit']
            
            if self.maxConn < permission[every]['max_cons_per_user']:
                self.maxConn = permission[every]['max_cons_per_user']
            
            if len(self.perms) < len(permission[every]['perm']):
                self.perms = permission[every]['perm']

            for each in permission[every]['operms']:
                if each not in self.operms:
                    self.operms[each] = permission[every]['operms'][each]
                else:
                    if len(self.operms[each]) < len(permission[every]['operms'][each]):
                        self.operms[each] = permission[every]['operms'][each]
        if self.username == 'attach':
            self.maxConn = 100

    def canList(self, path):
        path = self.homeDir + path
        dirs = self.operms.keys()
        perms = self.perms
        for each in dirs:
            if each in path:
                perms = self.operms[each]
        if 'l' not in perms:
            return False

        return True

    def canEnter(self, path):
        path = self.homeDir + path
        dirs = self.operms.keys()
        perms = self.perms
        for each in dirs:
            if each in path:
                perms = self.operms[each]
        if 'e' not in perms:
            return False

        return True

    def canRead(self, parent):

        dirs = self.operms.keys()
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]

        if 'r' not in perms:
            return False 

        return True
    
    def canWrite(self, parent):

        dirs = self.operms.keys()
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]
        
        if 'c' not in perms:
            return False 

        return True
    
    def canRemove(self, parent):

        dirs = self.operms.keys()
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]
        if 'd' not in perms:
            return False 

        return True
    def canRename(self, parent):
        dirs = self.operms.keys()
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]
        
        if 'f' not in perms:
            return False 

        return True
    
    def canMkDir(self, parent):
        dirs = self.operms.keys()
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]
        
        if 'm' not in perms:
            return False 

        return True


    def getDirPerm(self, name):

        dirs = self.operms.keys()
        perms = self.perms

        for each in dirs:
            if each in name:
                perms = self.operms[each]
        return perms

    def getFilePerm(self, parent):
        dirs = self.operms.keys()
        perm = ''
        perms = self.perms
        
        for each in dirs:
            if each in parent:
                perms = self.operms[each]
        if 'r' in perms:
            perm += 'r'
        if 'p' in perms:
            perm += 'd'
        if 'c' in perms:
            perm += 'afw'
        return perm
    
    def formatPath(self, arg):
        if arg[0] == '/':
            fullname = self.homeDir + arg 
            path = '/'.join(fullname.split('/')[:-1])
        elif self.currDir != '/':
            path = self.homeDir + self.currDir
            fullname = path + '/' + arg
        else:
            path = self.homeDir + self.currDir
            fullname = path + arg

        return (path, fullname)

    def getRemainQuote(self):
        dbConn = MySQLdb.connect( host = dbhost, user = dbuser, passwd = dbpasswd, db = dbname)
        cursor = dbConn.cursor()
        cmd = 'SELECT bytes_out_avail FROM ftpquotalimits WHERE name="%s";' % MySQLdb.escape_string(self.username)
        cursor.execute(cmd)
        totalQuote = cursor.fetchone()[0]

        cmd = 'SELECT bytes_out_used FROM ftpquotatallies WHERE name="%s";' % MySQLdb.escape_string(self.username)
        cursor.execute(cmd)
        usedQuote = cursor.fetchone()[0]

        cursor.close()
        dbConn.close()
        return (totalQuote - usedQuote, usedQuote, totalQuote)

    def updateQuote(self, transfer):
        self.usedQuote += transfer
        if self.usedQuote >= self.totalQuote:
            self.usedQuote = self.totalQuote
        
        if transfer != 0:
            dbConn = MySQLdb.connect( host = dbhost, user = dbuser, passwd = dbpasswd, db = dbname)
            cursor = dbConn.cursor()
            cmd = 'UPDATE ftpquotatallies SET bytes_out_used = %f WHERE name="%s";' % (self.usedQuote, MySQLdb.escape_string(self.username))
            cursor.execute(cmd)
        
        cursor.close()
        dbConn.close()

        self.remainingQuote = self.totalQuote - self.usedQuote

    def updateFileCount(self, filename):
        dbConn = MySQLdb.connect( host = dbhost, user = dbuser, passwd = dbpasswd, db = dbname)
        cursor = dbConn.cursor()
        cmd = 'SELECT * FROM filecount WHERE filename like "%s";' % MySQLdb.escape_string(filename)
        cursor.execute(cmd)
        name = cursor.fetchone()
        if name is None:
            cmd = "INSERT into filecount VALUES('%s', 1, '%s')" % (MySQLdb.escape_string(filename), MySQLdb.escape_string(self.username + '#'))
            cursor.execute(cmd)
        else:
            nowuser = self.username + '#'
            if nowuser not in name[2]:
                downloadUser = name[2] + nowuser
                cmd = 'UPDATE filecount SET filecount = filecount + 1, downuser = "%s" WHERE filename like "%s";' % (MySQLdb.escape_string(downloadUser), name[0])
                cursor.execute(cmd)

        cursor.close()
        dbConn.close()

    def localize(self, arg):
        unicodearg = arg
        newarg = arg
        if self.clientEncode == 'gbk':
            try:
                unicodearg = arg.decode('utf-8')
                newarg = unicodearg.encode('gbk')
            except:
                self.sendMsg(500, 'Cannot decode your command.')
        return newarg

    def serverlize(self, arg):
        unicodearg = arg
        newarg = arg
        if self.clientEncode == 'gbk':
            try:
                unicodearg = arg.decode('gbk')
                newarg = unicodearg.encode('utf-8')
            except:
                pass
        else:
            try:
                unicodearg = arg.decode('utf-8')
                newarg = unicodearg.encode('utf-8')
            except:
                unicodearg = arg.decode('gbk')
                newarg = unicodearg.encode('utf-8')

        return newarg

class fileDownloader(threading.Thread):
    def __init__(self, handler):
        threading.Thread.__init__(self)
        self.handler = handler

    def run(self):
            while self.handler.running:
                if self.handler.remainingQuote == 0:
                    self.handler.sendMsg(550, 'No enough quote. Please buy some at bbs.stuhome.net')
                    logger.info('===>' + 'User %s No Enough Quote' % self.handler.username)
                    self.handler.running = False
                    return
                self.handler.aliveTime = time.time()
                unitData = int(self.handler.downloadLimit * 1024 / 10)
                if unitData > MAX_BUFFER:
                    unitData = MAX_BUFFER
                if self.handler.remainingQuote < unitData:
                    unitData = int(self.handerl.remainingQuote)

                data = self.handler.fileFd.read(unitData)
                if len(data) == 0:
                    break
                sent = self.handler.dataFd.send(data)
                if FREE != True :
                    self.handler.updateQuote(sent)
                time.sleep(0.1)

            self.handler.fileFd.close()
            if self.handler.dataFd != 0:
                self.handler.dataFd.close()
                self.handler.dataFd = 0
            
            if self.handler.fd != 0:
                self.handler.sendMsg(226, 'Transfer complete.')
            logger.info('===>' + 'User %s Finished Download File %s. %d Byte Data Transfered' % (self.handler.username, self.handler.filePath.split('/')[-1],(self.handler.totalQuote -  self.handler.usedQuote)))

            time.sleep(1)
            self.handler.running = False


class fileUploader(threading.Thread):
    
    def __init__(self, handler):
        threading.Thread.__init__(self)
        self.handler = handler
        self.attachsize = 0
        self.attachmaxsize = ATTACH_MAXSIZE * 1024 * 1024
        self.uploadlimit = GlobalUploadLimit * 1024 * 1024 / 10 
        if self.handler.username == 'attach':
            self.uploadlimit = AttachUploadLimit * 1024 * 1024 / 10

    def run(self):
        while self.handler.running:
            self.handler.aliveTime = time.time()
            data = self.handler.dataFd.recv(int(self.uploadlimit))

            if len(data) <= 0:
                break
            self.handler.recvFile.write(data)
            if self.handler.username == 'attach':
                self.attachsize += len(data)
                if self.attachsize > self.attachmaxsize:
                    self.handler.sendMsg(550, 'File is too big.')
                    logger.info('===>' + 'Attach file is too big for user attch')
                    self.handler.running = False
                    self.handler.recvFile.close()
                    if self.handler.dataFd != 0:
                        self.handler.dataFd.close()
                        self.handler.dataFd = 0
                    return
            time.sleep(0.1)

        self.handler.recvFile.close()
        if self.handler.dataFd != 0:
            self.handler.dataFd.close()
            self.handler.dataFd = 0
           
        if self.handler.fd != 0:
            self.handler.sendMsg(226, 'Transfer complete.')
        logger.info('===>' + 'User %s Finished Upload File %s.' % (self.handler.username, self.handler.fullname))
        time.sleep(1)
        self.handler.running = False
